<template>
  <div class="map-config">
    <div class="map-form">
      <el-form
        v-loading="isLoading"
        ref="ruleFormRef"
        :rules="rules"
        :model="ruleForm"
        label-width="140px"
        style="width: 70%"
      >
        <el-form-item label="抽取任务名称：" prop="extractionTaskName">
          <el-input
            v-model="ruleForm.extractionTaskName"
            :disabled="curIsEdit"
            placeholder="请输入抽取任务名称"
          />
        </el-form-item>
        <el-form-item label="抽取类型：">
          <el-input value="结构化抽取方法" disabled />
        </el-form-item>
        <!-- 关联本体选择后 请求对应的本体类别和本体关系 -->
        <el-form-item label="请选择本体模型：" prop="ontologyAllId">
          <el-select
            v-model="ruleForm.ontologyAllId"
            placeholder="请选择本体"
            filterable
            :disabled="curIsEdit"
            style="width: 100%"
            @change="handleOntologyChange"
          >
            <el-option
              v-for="item in ontologyList"
              :key="item.ontologyAllId"
              :label="item.ontologyAllName"
              :value="item.ontologyAllId"
            >
            </el-option>
          </el-select>
        </el-form-item>
      </el-form>
      <el-button
        class="float-btn"
        type="primary"
        size="small"
        :disabled="curIsEdit"
        @click="startExtract"
        >开始抽取</el-button
      >
    </div>
    <div class="config-wrap">
      <el-tabs v-model="activeTab" class="config-tabs">
        <el-tab-pane
          v-for="item in tabMenus"
          :key="item.value"
          :name="item.value"
          :label="item.label"
        >
        </el-tab-pane>
      </el-tabs>
      <div class="rules-wrap entity" v-show="activeTab === '1'">
        <div
          class="rule-item"
          v-for="(entity, idx) in entityRuleList"
          :key="idx"
        >
          <div class="rule-head">
            <div class="rule-head-title">映射{{ idx + 1 }}：</div>
            <el-button
              :disabled="curIsEdit"
              type="text"
              @click="removeMap(idx)"
            >
              <i class="el-icon-delete"></i>
              删除
            </el-button>
          </div>
          <div class="rule-data">
            <div class="info-wrap">
              <el-row :gutter="10" style="flex: 1; overflow: hidden">
                <el-col :span="9">
                  <div class="info-item">
                    <div class="info-item-label require">实体类型</div>
                    <div class="info-item-content">
                      <el-select
                        v-model="entity.ontologyId"
                        filterable
                        :disabled="curIsEdit"
                        placeholder="请选择实体类别"
                        style="width: 100%"
                        @change="updatePropertyList"
                      >
                        <el-option
                          v-for="item in entityClassList"
                          :key="item.id"
                          :label="item.name"
                          :value="item.id"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </el-col>
                <el-col :span="9">
                  <div class="info-item">
                    <div class="info-item-label require">抽取对象</div>
                    <div class="info-item-content">
                      <el-select
                        v-model="entity.extractionObject"
                        filterable
                        :disabled="curIsEdit"
                        placeholder="请选择抽取对象"
                        style="width: 100%"
                      >
                        <el-option
                          v-for="(c, idx) in columns"
                          :key="idx"
                          :label="c"
                          :value="c"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </el-col>
                <el-col :span="6">
                  <div class="info-item">
                    <div class="info-item-label require">抽取策略</div>
                    <div class="info-item-content">
                      <el-select
                        v-model="entity.extractionStrategy"
                        filterable
                        :disabled="curIsEdit"
                        placeholder="请选择抽取策略"
                        style="width: 100%"
                      >
                        <el-option
                          v-for="s in strategyOptions"
                          :key="s.value"
                          :label="s.label"
                          :value="s.value"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </el-col>
              </el-row>
            </div>
            <!-- 用于实体映射属性为空时添加第一项 -->
            <el-button
              type="text"
              :disabled="curIsEdit"
              @click="addEntityProp(idx)"
              v-if="entity.attributes.length === 0"
            >
              <i class="el-icon-plus"></i>
              新增属性映射
            </el-button>
            <!-- 属性表头 -->
            <div
              class="info-wrap"
              style="margin-bottom: 0"
              v-if="entity.attributes.length > 0"
            >
              <el-row :gutter="10" style="flex: 1; overflow: hidden">
                <el-col :span="12">
                  <div class="info-item">
                    <div class="info-item-label require">属性名称</div>
                  </div>
                </el-col>
                <el-col :span="12">
                  <div class="info-item">
                    <div class="info-item-label require">抽取对象</div>
                  </div>
                </el-col>
              </el-row>
              <div class="info-opera" v-if="!curIsEdit"></div>
            </div>
            <!-- 属性输入框 -->
            <div
              class="info-wrap"
              v-for="(prop, rdx) in entity.attributes"
              :key="rdx"
            >
              <el-row :gutter="10" style="flex: 1; overflow: hidden">
                <el-col :span="12">
                  <div class="info-item">
                    <el-select
                      v-model="prop.propertyId"
                      filterable
                      :disabled="curIsEdit"
                      placeholder="请选择属性名"
                    >
                      <el-option
                        v-for="item in entityPropertyMap[entity.ontologyId]"
                        :key="item.propertyId"
                        :label="item.propertyName"
                        :value="item.propertyId"
                      >
                      </el-option>
                    </el-select>
                  </div>
                </el-col>
                <el-col :span="12">
                  <el-select
                    v-model="prop.extractionObject"
                    filterable
                    :disabled="curIsEdit"
                    placeholder="请选择抽取对象"
                  >
                    <el-option
                      v-for="(c, idx) in columns"
                      :key="idx"
                      :label="c"
                      :value="c"
                    >
                    </el-option>
                  </el-select>
                </el-col>
              </el-row>
              <div class="info-opera" v-if="!curIsEdit">
                <i
                  class="el-icon-circle-plus"
                  @click="addProperty(idx, rdx)"
                ></i>
                <i class="el-icon-remove" @click="removeProperty(idx, rdx)"></i>
              </div>
            </div>
          </div>
        </div>
      </div>
      <div class="rules-wrap relation" v-show="activeTab === '2'">
        <div
          class="rule-item"
          v-for="(relation, idx) in relationRuleList"
          :key="idx"
        >
          <div class="rule-head">
            <div class="rule-head-title">关系{{ idx + 1 }}：</div>
            <el-button
              :disabled="curIsEdit"
              type="text"
              @click="removeMap(idx)"
            >
              <i class="el-icon-delete"></i>
              删除
            </el-button>
          </div>
          <div class="rule-data">
            <div class="info-wrap">
              <el-row :gutter="10" style="flex: 1; overflow: hidden">
                <el-col :span="12">
                  <div class="info-item">
                    <div class="info-item-label require">头实体类</div>
                    <div class="info-item-content">
                      <el-select
                        v-model="relation.entityName1"
                        filterable
                        :disabled="curIsEdit"
                        placeholder="请选择头实体类"
                        style="width: 100%"
                        @change="relation.relationId = null"
                      >
                        <el-option
                          v-for="(val, key, idx) in columnEntityMap"
                          :key="idx"
                          :label="key"
                          :value="key"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </el-col>
                <el-col :span="12">
                  <div class="info-item">
                    <div class="info-item-label require">尾实体类</div>
                    <div class="info-item-content">
                      <el-select
                        v-model="relation.entityName2"
                        filterable
                        :disabled="curIsEdit"
                        placeholder="请选择尾实体类"
                        style="width: 100%"
                        @change="() => (relation.relationId = null)"
                      >
                        <el-option
                          v-for="(val, key, idx) in columnEntityMap"
                          :key="idx"
                          :label="key"
                          :value="key"
                        >
                        </el-option>
                      </el-select>
                    </div>
                  </div>
                </el-col>
              </el-row>
              <div class="info-opera"></div>
            </div>
            <div class="info-wrap">
              <div class="info-item">
                <div class="info-item-label require">关系名称</div>
                <div class="info-item-content">
                  <el-select
                    v-model="relation.relationId"
                    filterable
                    :disabled="curIsEdit"
                    placeholder="请选择关系"
                    style="width: 100%"
                  >
                    <el-option
                      v-for="item in getRelationOptions(
                        relation.entityName1,
                        relation.entityName2
                      )"
                      :key="item.id"
                      :label="item.name"
                      :value="item.id"
                    >
                    </el-option>
                  </el-select>
                </div>
              </div>
              <div class="info-opera"></div>
            </div>
          </div>
        </div>
      </div>
      <div class="add-rule-btn">
        <el-button :disabled="curIsEdit" style="width: 100%" @click="addMap">
          <i class="el-icon-plus"></i>
          新增{{ activeTab === "1" ? "实体映射" : "关系抽取" }}
        </el-button>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  name: "MapConfig",
  props: {
    columns: {
      type: Array,
      default: () => [],
    },
  },
  data() {
    return {
      ruleForm: {
        extractionTaskName: null,
        fileName: null,
        ontologyAllId: null,
        fileId: null,
        id: null,
      },
      rules: {
        extractionTaskName: [
          { required: true, message: "任务名称不能为空！", trigger: "blur" },
        ],
        ontologyAllId: [
          { required: true, message: "本体模型不能为空！", trigger: "blur" },
        ],
      },
      // 本体列表
      ontologyList: [],
      // 本体类查询条件
      entitySearchParams: {
        ontologyState: "0",
        pageNum: 1,
        pageSize: 1000,
      },
      // 本体类列表
      entityClassList: [],
      // 本体关系
      relationSearchParams: {
        relationState: "0",
        pageNum: 1,
        pageSize: 1000,
      },
      // 本体关系列表
      relationList: [],
      // 当前tab
      activeTab: "1",
      tabMenus: [
        { label: "实体抽取", value: "1" },
        { label: "关系抽取", value: "2" },
      ],
      // 抽取策略options
      strategyOptions: [
        {
          label: "去重",
          value: "去重",
        },
        {
          label: "不去重",
          value: "不去重",
        },
      ],
      // 实体映射数据
      entityRuleList: [
        {
          ontologyId: null, // 实体id
          ontologyName: null, // 实体名称
          extractionObject: null, // 列名
          extractionStrategy: "不去重",
          attributes: [
            {
              propertyId: null,
              propertyName: null,
              extractionObject: null,
            },
          ],
        },
      ],
      // 关系抽取数据
      relationRuleList: [
        // {
        //   entityName1: null, // 头实体绑定的列
        //   relationId: null,
        //   relationName: null,
        //   entityName2: null, // 尾实体绑定的列
        // },
      ],
      isLoading: false,
      // 当前是否编辑模式 判定条件为可以查询到配置信息为true 否则为false
      // 为true时不可编辑
      curIsEdit: false,
      // 存储实体id -> 实体名称映射
      entityIdNameMap: {},
      // 存储关系id -> 关系名称映射
      relationIdNameMap: {},
      // 存储实体id -> 属性id -> 属性名称映射
      entityPropertyIdNameMap: {},
      // 存储实体属性 ontologyId -> propertyList
      entityPropertyMap: {},
    };
  },
  computed: {
    // 实体类型和抽取列的映射 用于关系选择
    columnEntityMap() {
      const res = {};
      for (let item of this.entityRuleList) {
        // 实体id 和 抽取列值存在时才有效
        if (item.extractionObject && item.ontologyId) {
          res[item.extractionObject] = this.entityIdNameMap[item.ontologyId];
        }
      }
      return res;
    },
  },
  created() {
    // 如果路由包含参数 进行赋值
    this.ruleForm.extractionTaskName = this.$route.query.name;
    this.ruleForm.fileName = this.$route.query.fileName;
    this.ruleForm.fileId = this.$route.query.fileId;
    this.ruleForm.id = this.$route.query.id;
    // 请求
    this.getAllOntology();
    if (this.$route.query.id) {
      this.getMapConfigDetail();
    }
  },
  methods: {
    getMapConfigDetail() {
      this.$api.knowledgeGeneration
        .getStructExtractConfig({
          extractionDataRecordId: this.$route.query.id,
        })
        .then((res) => {
          const { message, code } = res.data;
          if (code === 200) {
            const resData = res.data.data;
            if (resData !== null && typeof resData === "object") {
              this.curIsEdit = true;
              Object.assign(this.ruleForm, resData);
              this.handleOntologyChange();
              // 尝试解析json数据
              try {
                // 实体
                if (resData.entityExtractionRule) {
                  this.entityRuleList = JSON.parse(
                    resData.entityExtractionRule
                  );
                  // 查询属性映射信息
                  this.entityRuleList.forEach((i) => {
                    this.updatePropertyList(i.ontologyId);
                  });
                }
                // 关系
                if (resData.relationExtractionRule) {
                  this.relationRuleList = JSON.parse(
                    resData.relationExtractionRule
                  );
                }
              } catch (error) {
                console.log(error);
              }
            }
          } else {
            this.$message.warning(message);
          }
        })
        .catch((err) => {
          console.log(err);
        });
    },
    // 获取全部本体模型
    getAllOntology() {
      this.$axios
        .get(this.$api.ontologyModel.xinbiaoObj.searchall)
        .then((res) => {
          if (res.data.code !== 200) {
            this.$message.error(res.data.message);
          } else {
            this.ontologyList = res.data.data || [];
          }
        })
        .catch();
    },
    // 获取本体模型下的本体类
    getOntologyClass() {
      return new Promise((resolve, reject) => {
        this.$axios
          .get(this.$api.ontologyModel.xinbiaoObj.searchontology, {
            params: {
              ontologyAllId: this.ruleForm.ontologyAllId,
              ...this.entitySearchParams,
            },
          })
          .then((res) => {
            if (res.data.code !== 200) {
              this.$message.error(res.data.message);
            } else {
              this.entityClassList = res.data.data.list || [];
              this.entityIdNameMap = {};
              this.entityClassList.forEach((e) => {
                this.entityIdNameMap[e.id] = e.name;
              });
            }
            resolve(true);
          })
          .catch((e) => {
            reject(e);
          });
      });
    },
    // 本体类关系
    getOntologyRelation() {
      return new Promise((resolve, reject) => {
        this.$axios
          .get(this.$api.ontologyModel.xinbiaoObj.searchRealation, {
            params: {
              ontologyAllId: this.ruleForm.ontologyAllId,
              ...this.relationSearchParams,
            },
          })
          .then((res) => {
            if (res.data.code !== 200) {
              this.$message.error(res.data.message);
            } else {
              this.relationList = res.data.data.list || [];
              this.relationIdNameMap = {};
              this.relationList.forEach((e) => {
                this.relationIdNameMap[e.id] = e.name;
              });
            }
            resolve(true);
          })
          .catch((e) => reject(e));
      });
    },
    // 关联本体变化 查询实体和关系
    handleOntologyChange() {
      this.getOntologyClass();
      this.getOntologyRelation();
      this.entityPropertyIdNameMap = {};
    },
    // 选中实体类后 查询属性
    updatePropertyList(id) {
      if (id in this.entityPropertyMap) {
        return;
      } else {
        this.$axios
          .get(this.$api.knowledgeMap.ontologyProperty, {
            params: { ontologyId: id },
          })
          .then((res) => {
            let code = res.data.code;
            if (code !== 200) {
              this.$message.warning(res.data.message);
            } else {
              if (Array.isArray(res.data.data) && res.data.data.length) {
                this.$set(this.entityPropertyMap, id, res.data.data);
                // 存储实体id-属性id：属性name
                let tmpMap = {};
                res.data.data.forEach((e) => {
                  tmpMap[e.propertyId] = e.propertyName;
                });
                this.entityPropertyIdNameMap[id] = tmpMap;
              }
            }
          });
      }
    },
    // 查询关系
    getRelationOptions(from, to) {
      if (!from && !to) return [];
      let fromName = this.columnEntityMap[from];
      let toName = this.columnEntityMap[to];
      if (fromName && toName) {
        return this.relationList.filter(
          (i) => i.sourceName === fromName && i.targetName === toName
        );
      } else {
        if (fromName) {
          return this.relationList.filter((i) => i.sourceName === fromName);
        } else {
          return this.relationList.filter((i) => i.targetName === toName);
        }
      }
    },
    // 添加实体属性第一项
    addEntityProp(idx) {
      this.entityRuleList[idx].attributes.push({
        propertyId: null,
        propertyName: null,
        extractionObject: null,
      });
    },
    // 添加映射
    addMap() {
      if (this.activeTab === "1") {
        this.entityRuleList.push({
          ontologyId: null,
          ontologyName: null,
          extractionObject: null,
          extractionStrategy: "不去重",
          attributes: [],
        });
      } else if (this.activeTab === "2") {
        this.relationRuleList.push({
          entityName1: null,
          relationId: null,
          relationName: null,
          entityName2: null,
        });
      }
    },
    // 移除映射
    removeMap(idx) {
      if (this.activeTab === "1") {
        this.entityRuleList.splice(idx, 1);
      } else if (this.activeTab === "2") {
        this.relationRuleList.splice(idx, 1);
      }
    },
    // 添加属性
    addProperty(idx, rdx) {
      this.entityRuleList[idx].attributes.splice(rdx, 0, {
        propertyId: null,
        propertyName: null,
        extractionObject: null,
      });
    },
    // 移除属性
    removeProperty(idx, rdx) {
      this.entityRuleList[idx].attributes.splice(rdx, 1);
    },
    // 校验并转换提交信息
    transSubmitInfo() {
      // 实体映射
      if(this.entityRuleList.length ==0) {
        return false;
      }
      for (let e of this.entityRuleList) {
        if (!e.ontologyId) {
          this.activeTab = "1";
          return this.$message.warning("请选择实体类型");
        } else if (!e.extractionObject) {
          this.activeTab = "1";
          return this.$message.warning("请选择抽取对象");
        } else {
          e.ontologyName = this.entityIdNameMap[e.ontologyId];
        }
        // 处理属性
        if (e.attributes.length) {
          for (let p of e.attributes) {
            if (!p.propertyId) {
              this.activeTab = "1";
              return this.$message.warning("请选择属性");
            } else if (!p.extractionObject) {
              this.activeTab = "1";
              return this.$message.warning("请选择抽取对象");
            } else {
              p.propertyName =
                this.entityPropertyIdNameMap[e.ontologyId][p.propertyId];
            }
          }
        }
      }
      // if(this.relationRuleList.length ==0) {
      //   return false;
      // }
      // 关系映射
      for (let r of this.relationRuleList) {
        if (!r.entityName1) {
          this.activeTab = "2";
          return this.$message.warning("请选择头实体类");
        } else if (!r.entityName2) {
          this.activeTab = "2";
          return this.$message.warning("请选择尾实体类");
        } else if (!r.relationId) {
          this.activeTab = "2";
          return this.$message.warning("请选择关系");
        } else {
          r.relationName = this.relationIdNameMap[r.relationId];
        }
      }
      return true;
    },
    // 准备参数
    readySubmitForm() {
      this.ruleForm.entityExtractionRule = JSON.stringify(this.entityRuleList);
      this.ruleForm.relationExtractionRule = JSON.stringify(
        this.relationRuleList
      );
    },
    // 开始抽取
    startExtract() {
      this.$refs.ruleFormRef.validate((valid) => {
        if (valid) {
          const res = this.transSubmitInfo();
          if (res === true) {
            this.readySubmitForm();
            this.$api.knowledgeGeneration.startStructExtract(this.ruleForm);
            this.$message.success("提交成功，请等待结构化抽取任务执行");
            setTimeout(() => {
              this.$router.replace("/knowledgeGeneration/extractTask");
            }, 300);
          } else {
            this.$message.error("实体映射配置不能为空！");
          }
        }
      });
    },
  },
};
</script>
<style lang="scss" scoped>
/deep/ .el-form-item.bold .el-form-item__label {
  font-weight: bold;
}

.map-config {
  position: relative;
  display: flex;
  flex-direction: column;
}

.float-btn {
  position: absolute;
  right: 20px;
  top: 20px;
}

.config-wrap {
  position: relative;
  flex: 1;
  overflow: hidden;
  overflow-y: auto;
}

.config-tabs {
  position: sticky;
  top: 0;
  z-index: 10;
  background: #fff;
}

.add-rule-btn {
  position: sticky;
  bottom: 0;
  z-index: 10;
  background: #fff;
}

.rule-item {
  padding: 10px 20px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
  margin-bottom: 20px;
}

.rule-head {
  display: flex;
  justify-content: space-between;
}

.rule-head-title {
  font-size: 16px;
  font-weight: 700;
}

.info-wrap {
  position: relative;
  display: flex;
  align-items: center;
  margin-bottom: 20px;
  .info-item {
    flex: 1;
    overflow: hidden;
  }
  .info-opera {
    width: 70px;
    margin-left: 10px;
  }
}

.info-item-label {
  // box-sizing: border-box;
  margin: 10px 0;
  &.require::before {
    content: "*";
    color: $--color-danger;
    margin-right: 4px;
  }
}
.info-item-content {
  flex: 1;
  overflow: hidden;
}
.illegal-tips {
  position: absolute;
  left: 100px;
  bottom: -30px;
  padding-top: 4px;
  font-size: 12px;
  color: $--color-danger;
}

.el-icon-circle-plus {
  margin: 0 5px;
  font-size: 16px;
  color: $--color-primary;
  cursor: pointer;
}
.el-icon-remove {
  margin: 0 5px;
  font-size: 16px;
  color: $--color-danger;
  cursor: pointer;
}
</style>
